package com.example.shiro.config;

import com.example.shiro.realm.AuthRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @description Shiro 配置类
 *          参考：http://www.cnblogs.com/ityouknow/p/7089177.html
 *          https://blog.csdn.net/weixin_38132621/article/details/80216056
 *          https://www.cnblogs.com/expiator/p/8651798.html
 *          https://blog.csdn.net/u013615903/article/details/78781166/
 */
@Configuration
public class ShiroConfig {

    /**
     * @description Shiro Filter 拦截器相关配置
     *  1、过滤连注释：
     *      anon            无参，开放权限，可以理解为匿名用户或游客
     *      authc           无参，需要认证
     *      logout          无参，注销，执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url
     *      authcBasic      无参，表示 httpBasic 认证
     *      user            无参，表示必须存在用户，当登入操作时不做检查
     *      ssl             无参，表示安全的URL请求，协议为 https
     *      perms[user]     参数可写多个，表示需要某个或某些权限才能通过，多个参数时写 perms[“user, admin”]，当有多个参数
     *                      时必须每个参数都通过才算通过
     *      roles[admin]    参数可写多个，表示是某个或某些角色才能通过，多个参数时写 roles[“admin，user”]，当有多个参数
     *                      时必须每个参数都通过才算通过
     *      rest[user]      根据请求的方法，相当于 perms[user:method]，其中 method 为 post，get，delete 等
     *      port[8081]      当请求的URL端口不是8081时，跳转到schemal://serverName:8081?queryString 其中 schmal 是协
     *                      议 http 或 https 等等，serverName 是你访问的 Host，8081 是 Port 端口，queryString 是你访问
     *                      的 URL 里的 ? 后面的参数
     *
     *  2、  注意：anon, authc, authcBasic, user 是第一组认证过滤器，perms, port, rest, roles, ssl 是第二组授权过滤器，
     *          要通过授权过滤器，就先要完成登陆认证操作（即先要完成认证才能前去寻找授权) 才能走第二组授权器（例如访问需要 roles
     *          权限的 url，如果还没有登陆的话，会直接跳转到 shiroFilterFactoryBean.setLoginUrl(); 设置的 url ）
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        System.out.println("-------------Shiro拦截器工厂 ShiroConfig.shiroFilter() 开始------------");

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(securityManager);     // 设置shiro 安全管理器 (必须设置)
        shiroFilterFactoryBean.setLoginUrl("/login");                   // 设置默认登录页面(如果不设置值，默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射)
        shiroFilterFactoryBean.setSuccessUrl("/index");                 // 设置登录成功后的跳转页面

        /**
         * 自定义过滤器 (过滤链定义，从上向下顺序执行，一般将 "/**" 放在最为下边,这是一个坑，要不一不小心代码就不好使了)
         * */
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        /* 设置 controller 里不被拦截的url   start*/
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/doLogin", "anon");
        /* 设置 controller 里不被拦截的url   end*/

        filterChainDefinitionMap.put("/js/**", "anon");      // 设置 静态资源不被拦截
        filterChainDefinitionMap.put("/css/**", "anon");     // 设置 静态资源不被拦截
        filterChainDefinitionMap.put("/logout", "logout");   // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/**", "authc");        // 主要这行代码必须放在所有权限设置的最后，不然会导致所有 url 都被拦截

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        System.out.println("-------------Shiro拦截器工厂类注入成功-------------");
        return shiroFilterFactoryBean;
    }

    /**
     *  1、注入 shiro 安全管理器 SecurityManager
     *
     *  提醒：此方法还可以做一下事情：
     *      （1） 实现自定义 session 管理
     *      （2） 实现 “记住我” 功能
     *      ...
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        // 1、设置自定义 realm
        defaultWebSecurityManager.setRealm(myShiroRealm());

        return defaultWebSecurityManager;
    }

    /**
     *  2、注入 自定义认证 realm 类
     */
    @Bean
    public AuthRealm myShiroRealm() {
        return new AuthRealm();
    }
}
